Tokom evolucije, dolazilo je do razdvajanja vrsta. Kako su se vrste udaljavale dolazilo je do pojave izmena u DNK sekvenci. Neki regioni su mutirali vise, neki manje. Regioni DNK koji su kodirali najbitnije proteine za opstanak jedinke nisu trpeli velike izmene (u protivnom jedinka ne bi prezivela), ali su se desavale izmene u redosledu navedenih regiona.
Primer: Neka je redosled određenih regiona kod jedne vrste: $A \rightarrow B \rightarrow C \rightarrow D$
Kod druge vrste, koja se od zajedničkog pretka sa prvom vrstom redosled istih regiona je: $D \rightarrow C \rightarrow A \rightarrow B$
Između regiona postoje sekvence proizvoljne
Sekvence u regionima mogu biti inverzne (sekvenca se cita u obrnutom redosledu u odnosu na pretka): $(-D) \rightarrow C \rightarrow (-A) \rightarrow B$
Preciznije, neka su regioni A = ATAG, B = CCGT, C = GCTG, D = TTTA
Jedna vrsta:
$A \rightarrow B \rightarrow C \rightarrow D$ = ATAG...CCGT...GCTG...TTTA
Druga vrsta:
$(-D) \rightarrow C \rightarrow (-A) \rightarrow B$ = ATTT...GCTG...ATAG...CCGT
Regioni koji se posmatraju u ovom kontekstu se nazivaju sinteni blokovi (eng Synteny Blocks). Razlike u redosledu i pravcu citanja sinteni blokova nastaju usled savijanja i presecanja DNK. Pojedinačne promene koje dovode do inverzije redosleda sinteni blokova nazivaju se inverzije.

Primer: $A \rightarrow | B \rightarrow C \rightarrow D |$ Posle inverzije u označenom regionu: $A \rightarrow (-D) \rightarrow (-C) \rightarrow (-B)$
Broj inverzija između dve vrste često je srazmeran periodu koji je protekao od odvajanja vrsta od zajedničkog pretka do sadašnjosti.

Jedan od pristupa za računanje broja inverzija je, naravno, pristup grubom silom. Kako je ovaj pristup veoma neefikasan, može se probati sa heurističkim, znatno bržim pristupom. Jedan takav, heuristički, pristup je Sorting by Reversals. Sinteni blokovi jedne vrste označe se redom brojevima od 1 do $n$ (gde je $n$ ukupan broj blokova) dok se odgovarajući blokovi druge vrste označavaju oznakama kojim su označeni u prvoj vrsti.
Primer: Prva vrsta: $A \rightarrow D \rightarrow C \rightarrow B$ = $[1, 2, 3, 4,]$
Druga vrsta: $D \rightarrow (-A) \rightarrow C \rightarrow (-B)$ = $[2, -1, 3, -4,]$
Polazeći od drugog, neuređenog niza, primenom inverzija se niz dovodi do uređenog niza poziciju po poziciju.
Neka je početno stanje: $[2, -1, 3, -4,]$, kako je na prvoj poziciji pogrešan blok, potrebno je na prvu poziciju dovesti blok broj 1.
$[2^*, -1, 3, -4,]$
$[|2, -1|, 3, -4,]$
$[|1, -2|, 3, -4,]$
Ukupni broj operacija potrebnih za dovođenje nesortiranog niza do sortiranog predstavlja broj inverzija između dve vrste. Ovo je, kao što je napomenuto, heuristički pristup koji ne vraća uvek najmanji broj inverzija. Kako je očekivano da je evolucija ipak išla najkraćim putem, ima smisla tragati i za boljim rešenjem.
def apply_sorting_reversal(P, k):
n = len(P)
for i in range(k, n):
if abs(P[i]) == k + 1:
L = P[:k]
Mid = P[k: i+1]
Mid = [-x for x in Mid[::-1]]
R = P[i + 1: n]
return L + Mid + R
def greedy_sort(P):
approx_reversal_distance = 0
# Optional
print(P)
n = len(P)
for k in range(n):
if P[k] != (k + 1):
P = apply_sorting_reversal(P, k)
approx_reversal_distance += 1
print(P)
if P[k] == -(k + 1):
P[k] = -P[k]
approx_reversal_distance += 1
print(P)
return approx_reversal_distance
P = [+1, -7, +6, -10, +9, -8, +2, -11, -3, +5, +4]
greedy_sort(P)
Drugi pristup dolazi iz sveta grafova. Niz sinteni blokova predstavi se kao cikličan, umesto do sada linearne pretpostavke. Jedan sinteni blok predstavlja se usmerenom granom grafa između dva čvora, simbolizujući pravac čitanja bloka. Takođe, kako je moguće da se blokovi nalaze u različitim hromozomima, formiraju se posebni ciklusi za blokove svakog hromozoma.
Grane koje predstavljaju blokove oznacene su crnom bojom dok su grane između blokova (koje predstavljaju sekvence između sinteni blokova) označavaju obojenim granama.

Jedna inverzija na hromozomu rezultovaće dvostrukim ukrštanjem grana (2 break) grafa, na način prikazan na slici:
Preseci koji predstavljaju inverzije mogu se desiti samo nad obojenim granama.
Da bi se izračunao broj inverzija između dve vrste (grupe hromozoma), formira se zajednički, Breakpoint graf, preklapanjem grana obe grupe. Zatim, računa se broj potrebnih inverzija kako bi se dobijeni graf "razmrsio", tj. doveo do grafa koji sadrži samo proste cikluse.

Traženi broj inverzija dobija se uzastopnim pravljenjem preseka nad Breakpoint grafom, tačnije, presecanjem grana jedne boje (npr. samo crvene, gde su crvenim grana predstaljeni hromozomi jedne vrste) koje pripadaju ciklusima sa granama druge boje, sve dok graf ne postane prost (sve obojene grane se poklapaju između istih čvorova).

Naredni kodovi odgovaraju pseudokodovima iz udžbenika i primeri su postavljeni da odgovaraju primerima sa slika.
def chromosome_to_cycle(chromosome):
n = len(chromosome)
nodes = [0] * (n*2)
for j in range(n):
i = chromosome[j]
if i > 0:
nodes[2 * j] = (2 * i) - 1
nodes[2 * j + 1] = (2 * i)
else:
nodes[2 * j] = - (2 * i)
nodes[2 * j + 1] = - (2 * i) - 1
return nodes
chromosome_to_cycle([1, 2, 3, 4, 5, 6])
def cycle_to_chromosome(nodes):
n = len(nodes)
chromosome = [0] * (n//2)
for j in range(0, n, 2):
if nodes[j] < nodes[j + 1]:
chromosome[j // 2] = nodes[j + 1] // 2
else:
chromosome[j // 2] = - nodes[j] // 2
return chromosome
cycle_to_chromosome([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
def colored_edges(P):
edges = []
for chromosome in P:
nodes = chromosome_to_cycle(chromosome)
n = len(nodes)
for j in range(1, n - 1, 2):
edges.append((nodes[j], nodes[j + 1]))
edges.append((nodes[n - 1], nodes[0]))
return edges
P = [[+1, -2, -3, +4]]
nodes = chromosome_to_cycle(P[0])
print(nodes)
chromosome = cycle_to_chromosome(nodes)
print(chromosome)
P_colored_edges = colored_edges(P)
print(P_colored_edges)
def black_edges(P):
edges = []
for chromosome in P:
nodes = chromosome_to_cycle(chromosome)
n = len(nodes)
for j in range(0, n - 1, 2):
edges.append((nodes[j], nodes[j + 1]))
return edges
print(black_edges(P))
Klasa koja predstavlja graf
class GenomeGraph():
def __init__(self, edges):
G = {}
for edge in edges:
(v, w) = edge
if v not in G:
G[v] = [w]
else:
G[v].append(w)
if w not in G:
G[w] = [v]
else:
G[w].append(v)
self.G = G
def get_cycles(self):
unvisited = set(self.G.keys())
cycles = []
while len(unvisited) > 0:
start = min(unvisited)
v = start
unvisited.remove(start)
cycle = []
while True:
next_w = None
for w in self.G[v]:
if w in unvisited:
next_w = w
break
if next_w == None:
cycle.append((v,start))
break
cycle.append((v, next_w))
v = w
unvisited.remove(w)
cycles.append(cycle)
return cycles
def remove_edge(self, edge):
(v, w) = edge
self.G[v].remove(w)
self.G[w].remove(v)
def add_edge(self, edge):
(v, w) = edge
self.G[v].append(w)
self.G[w].append(v)
P = [[1,-2,-3,4], [5,6]]
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
genome_graph.get_cycles()
def nodes_on_path(path):
nodes = []
for (v, w) in path:
nodes.append(v)
return nodes
Pretvaranje hromozoma u graf
print (P)
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
cycles = genome_graph.get_cycles()
print(cycles)
for cycle in cycles:
print(nodes_on_path(cycle))
def graph_to_genome(genome_graph):
P = []
cycles = genome_graph.get_cycles()
for cycle in cycles:
nodes = nodes_on_path(cycle)
chromosome = cycle_to_chromosome(nodes)
P.append(chromosome)
return P
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
graph_to_genome(genome_graph)
2-break nad grafom:
def two_break_on_genome_graph(genome_graph, i, i_p, j, j_p):
genome_graph.remove_edge((i, i_p))
genome_graph.remove_edge((j, j_p))
genome_graph.add_edge((i, j))
genome_graph.add_edge((i_p, j_p))
return genome_graph
P = [[1, -2, -3, 4]]
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
print(genome_graph.get_cycles())
genome_graph = two_break_on_genome_graph(genome_graph, 1, 8, 3, 6)
print(genome_graph.get_cycles())
def two_break_on_genome(P, i, i_p, j, j_p):
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
genome_graph = two_break_on_genome_graph(genome_graph, i, i_p, j, j_p)
P = graph_to_genome(genome_graph)
return P
P = [[1, -2, -3, 4]]
print(P)
P = two_break_on_genome(P, 1, 8, 3, 6)
print(P)
P = [[1, 2, 3, 4]]
print(P)
genome_graph = GenomeGraph(black_edges(P) + colored_edges(P))
genome_graph = two_break_on_genome_graph(genome_graph, 2, 3, 4, 5)
genome_graph = two_break_on_genome_graph(genome_graph, 5, 3, 7, 6)
P = graph_to_genome(genome_graph)
print(P)
import random
def shorthest_rearangement_scenario(P, Q):
print(P)
red_edges = colored_edges(P)
blue_edges = colored_edges(Q)
breakpoint_graph = GenomeGraph(red_edges + blue_edges)
while True:
cycles = breakpoint_graph.get_cycles()
selected_cycle = None
for cycle in cycles:
n = len(cycle)
if n > 2:
selected_cycle = cycle
break
if selected_cycle == None:
break
k = random.randrange(0, n)
edge = selected_cycle[k]
(v, w) = edge
if (v, w) not in blue_edges:
(w, v) = (v, w)
if (v, w) not in blue_edges:
k = (k + 1) % n
(j, i_p) = (v, w)
left_red_edge = selected_cycle[(k - 1) % n]
right_red_edge = selected_cycle[(k + 1) % n]
(i, j) = left_red_edge
(i_p, j_p) = right_red_edge
breakpoint_graph.remove_edge(left_red_edge)
breakpoint_graph.remove_edge(right_red_edge)
breakpoint_graph.add_edge((j, i_p))
breakpoint_graph.add_edge((i, j_p))
P = two_break_on_genome(P, j, i, i_p, j_p)
print(P)
P = [[1, -2, -3, 4]]
Q = [[1, 2, 3, 4]]
shorthest_rearangement_scenario(P, Q)
P = [[-6, 1, -2, 3, 4, 5]]
Q = [[1, 2, 3, 4, 5, 6]]
shorthest_rearangement_scenario(P, Q)